#include "StdAfx.h"
#include "ExPortal.h"
#include "ExUtilityFunction.h"

#include "ConvexHullAlgorithm.h"

#include "3rd_part\xml\tixmlutility.h"
#include "ray.h"


ExPortal::ExPortal(void)
{
}


ExPortal::~ExPortal(void)
{
}

bool ExPortal::Init( INode* pMaxNode, Mesh* pPortalShape, TimeValue timeNow, bool snapVertices )
{
	if( !pPortalShape->getNumFaces() || !pPortalShape->getNumVerts() )
		return false;

	m_PortalVectors.clear();

	m_matPortalMatrixl = pMaxNode->GetObjectTM( timeNow );
	bool inverted = ExUtilityFunction::IsMirrored( m_matPortalMatrixl );
	Common::Vector3 point1, point2, point3;
	for( int i = 0; i < pPortalShape->getNumFaces(); i++ )
	{
		for( int iFace = 0; iFace != 3; ++iFace )
		{
			if( inverted )
			{
				ExUtilityFunction::Convert( pPortalShape->getVert( pPortalShape->faces[ i ].v[ 2 ] ), point1 );
				ExUtilityFunction::Convert( pPortalShape->getVert( pPortalShape->faces[ i ].v[ 1 ] ), point2 );
				ExUtilityFunction::Convert( pPortalShape->getVert( pPortalShape->faces[ i ].v[ 0 ] ), point3 );

				AddVertex( point1, snapVertices );
				AddVertex( point2, snapVertices );
				AddVertex( point3, snapVertices );
			}
			else
			{
				ExUtilityFunction::Convert( pPortalShape->getVert( pPortalShape->faces[ i ].v[ 0 ] ), point1 );
				ExUtilityFunction::Convert( pPortalShape->getVert( pPortalShape->faces[ i ].v[ 1 ] ), point2 );
				ExUtilityFunction::Convert( pPortalShape->getVert( pPortalShape->faces[ i ].v[ 2 ] ), point3 );

				AddVertex( point1, snapVertices );
				AddVertex( point2, snapVertices );
				AddVertex( point3, snapVertices );
			}
		}
	}

	return this->CreateConvexHull();
}

int  ExPortal::AddVertex( const Common::Vector3& bv, bool snapVertices )
{
	Point3 maxBV;
	ExUtilityFunction::Convert( bv, maxBV );

	//Point3 ppp = ExUtilityFunction::ApplyUnitScale( maxBV * m_matPortalMatrixl );
	Point3 ppp = maxBV * m_matPortalMatrixl;
	Common::Vector3 vNewPoint;
	if ( snapVertices )
	{
		vNewPoint = ExUtilityFunction::SnapPoint3(ppp);
		ExUtilityFunction::Convert( vNewPoint, ppp );
	}

	ItrPortalVectors itrPortalVector = std::find( m_PortalVectors.begin(), m_PortalVectors.end(), vNewPoint );
	if( itrPortalVector == m_PortalVectors.end() )
	{
		m_PortalVectors.push_back(vNewPoint);
		return m_PortalVectors.size();
	}
	return std::distance( m_PortalVectors.begin(), itrPortalVector );
}

bool ExPortal::CreateConvexHull(void)
{
	//convert the point
	GrahamScan portalConvex( m_PortalVectors );
	return true;
}

bool ExPortal::CullHull( const std::vector<Common::Plane>& rBoundaryPlanes, bool& bIsCulled )
{
	bIsCulled = false;
	float epsilon = 0.0001f;

	// Iterate through the boundaries
	std::vector<Common::Plane>::const_iterator itrBoundary = rBoundaryPlanes.begin();
	for( ; itrBoundary != rBoundaryPlanes.end(); ++itrBoundary )
	{
		// Adjust plane to account for floating point inaccuracies
		Common::Plane testPlane( -(*itrBoundary).normal, -(*itrBoundary).d + epsilon );
		bIsCulled |= _CullByPlane( m_PortalVectors, testPlane );
	}

	return !m_PortalVectors.empty();
}

bool ExPortal::_CullByPlane( std::vector<Common::Vector3>& convex, const Common::Plane& plane )
{
	bool culled = false;
	std::vector<Common::Vector3> newConvex;

	Common::Ray rayIntersect;
	for( std::vector<Common::Vector3>::iterator iter = convex.begin();
		convex.size() > 2 && iter != convex.end(); ++iter )
	{
		//add an edge on the convex are in the same side
		//cull an edge on the different side
		std::vector<Common::Vector3>::iterator iter2 = ( iter + 1 != convex.end() ) ? iter + 1 : convex.begin();
		if( plane.getSide( *iter ) == Common::Plane::NEGATIVE_SIDE )
		{
			newConvex.push_back( *iter );
			if( plane.getSide( *iter2 ) == Common::Plane::POSITIVE_SIDE )
			{
				rayIntersect.setOrigin( *iter );
				rayIntersect.setDirection( *iter2 - *iter );
				std::pair< bool, Real > result = rayIntersect.intersects( plane );
				assert( result.first == true );
				newConvex.push_back( rayIntersect.getPoint( result.second ) );
				culled = true;
			}
		}
		else
		{
			if( plane.getSide( *iter2 ) == Common::Plane::NEGATIVE_SIDE )
			{
				rayIntersect.setOrigin( *iter );
				rayIntersect.setDirection( *iter2 - *iter );
				std::pair< bool, Real > result = rayIntersect.intersects( plane );
				assert( result.first == true );
				newConvex.push_back( rayIntersect.getPoint( result.second ) );
				culled = true;
			}
		}
	}
	convex = newConvex;
	if( convex.size() < 3 )
		convex.clear();

	return culled;
}



/*
<portal>
<uAxis>	1.000000 0.000000 0.000000	</uAxis>
<point>	-2.000000 4.000000 0.000000	</point>
<point>	2.000000 4.000000 0.000000	</point>
<point>	2.000000 0.000000 0.000000	</point>
<point>	-2.000000 0.000000 0.000000	</point>
</portal>
*/
bool ExPortal::Save( TiXmlNode* pBoundarySection )
{
	assert( (m_PortalVectors.size() >= 3) && pBoundarySection != NULL );
	if( m_PortalVectors.size() < 3 || pBoundarySection == NULL ) 
		return false;
	//change back because the serialzed data has converted to DX already!
	Common::Plane boundary = TiXmlUtility::ReadPlane( pBoundarySection );
	//boundary.d *=  100 / (float)GetMasterScale( UNITS_CENTIMETERS );
	//boundary = ExUtilityFunction::DxToMax( boundary );

	PortalVectors DXPortalVectors;
	for( int iPVIdx = 0; iPVIdx != m_PortalVectors.size(); ++iPVIdx )
		DXPortalVectors.push_back( ExUtilityFunction::MaxToDx( ExUtilityFunction::ApplyUnitScale( m_PortalVectors[iPVIdx] ) ) );

	Common::Vector3 uAxis = DXPortalVectors[1] - DXPortalVectors[0];
	uAxis.normalise();

	Common::Vector3 vNormal = boundary.normal;
	Common::Vector3 vAxis = vNormal.crossProduct( uAxis );

	Common::Matrix4 matBoundarySpace( Common::Matrix4::IDENTITY );
	matBoundarySpace[0][0] = uAxis[0], matBoundarySpace[1][0] = uAxis[1], matBoundarySpace[2][0] = uAxis[2];
	matBoundarySpace[0][1] = vAxis[0], matBoundarySpace[1][1] = vAxis[1], matBoundarySpace[2][1] = vAxis[2];
	matBoundarySpace[0][2] = vNormal[0], matBoundarySpace[1][2] = vNormal[1], matBoundarySpace[2][2] = vNormal[2];
	matBoundarySpace.setTrans( boundary.normal * boundary.d );

	Common::Matrix4 matBoundarySpaceI = matBoundarySpace.inverse();

	TiXmlElement rPortalSection("portal");
	//points
	for ( ItrPortalVectors pos = DXPortalVectors.begin(); pos != DXPortalVectors.end(); ++pos )
	{
		TiXmlElement rPoint("point");
		//transform the portal points into boundary space
		Common::Vector3 ptsInBS = matBoundarySpaceI * *pos;
		rPoint.InsertEndChild( TiXmlText( TiXmlUtility::Vector3ToString( ptsInBS ) ) ) ;

		rPortalSection.InsertEndChild(rPoint);
	}
	//Axis
	TiXmlElement rAxisNode("uAxis");
	rAxisNode.InsertEndChild( TiXmlText( TiXmlUtility::Vector3ToString( uAxis ) ) );
	rPortalSection.InsertEndChild(rAxisNode);

	pBoundarySection->InsertEndChild(rPortalSection);

	return true;
}

int ExPortal::OnBoundary( const std::vector<Common::Plane>& rBoundaryPlanes )
{
	Common::Plane portalPlane( m_PortalVectors[0], m_PortalVectors[1], m_PortalVectors[2] );

	float fDistance = 0.0f;
	for ( int iBoundaryIdx = 0; iBoundaryIdx < rBoundaryPlanes.size(); iBoundaryIdx++ )
	{
		if ( !portalPlane.getDistance( rBoundaryPlanes[iBoundaryIdx] , fDistance ) )
			continue;

		if( ::fabs(fDistance) < 1.0f )
			return iBoundaryIdx;
	}

	//make the portal revised and match again
	std::reverse( m_PortalVectors.begin(), m_PortalVectors.end() );

	portalPlane.redefine( m_PortalVectors[0], m_PortalVectors[1], m_PortalVectors[2] );
	for ( int iBoundaryIdx = 0; iBoundaryIdx < rBoundaryPlanes.size(); iBoundaryIdx++ )
	{
		if ( !portalPlane.getDistance( rBoundaryPlanes[iBoundaryIdx] , fDistance ) )
			continue;

		if( ::fabs(fDistance) < 1.0f )
			return iBoundaryIdx;
	}
	return -1;
}